/*
 * File:   File.h
 * Author: Maxwell
 *
 * Created on February 28, 2011, 8:15 PM
 */

#include "File.h"
#include "FCB.h"

using namespace std;

/**
 * The Initialize method must be called before any File objects are created.  This
 * method is for intializing the FreeList and Directory variables so that the file
 * class has access to them.
 *
 * @param f Freelist for the Disk.
 * @param d Directory for the Disk.
 */
void File::Initialize(FreeList *f, Directory *d) {

    if(f == NULL || d == NULL) {
        cout << "ERROR: Variables passed into File::Initialize(freelist,directory) are invalid" << endl;
        throw new FileException();
    }

    m_freelist = f;
    m_directory = d;
    m_disk = f->GetDisk();

    cout << "Successfully Initialized the File class variables" << endl;
}

/**
 * The file constructor checks the directory for the passed-in
 * filename to see if it already exists.  If it doesn't exist, it creates the new file.
 * If the file exists, the file constructor re-opens the file for reading/writing.
 * The mode variable will either contain the value r for reading or w for writing.
 * When the file is either created or re-opened, the mode value will determine whether
 * the file will be able to written to or read to, not both.
 *
 * @param name Filename of the new or existing file.
 * @param mode Mode char will either be w for writing to the file or r for reading.
 */
File::File(char* name, char mode){

    if (name == NULL || (mode != 'w' && mode != 'r')) {
        cout << "ERROR: Variables passed into File::File(name,mode) are invalid" << endl;
        throw new FileException();
    }

    m_mode = mode;
    //printf("test \n");
    bool fileExists;
    //printf("test \n");
    fileExists = Exists(name);
    //printf("test \n");
    // File Exists
 if(fileExists) {
     m_fcb = new FCB(m_directory->Find(name), m_disk);
     this->Open(m_mode);
//        m_fcb = new FCB(m_directory->Find(name), m_disk);
//        m_blockgroup = new BlockGroup(m_fcb->getFileStartBlockNumber(), m_fcb->getFileEndBlockNumber(),
//                m_fcb->getFileNumberOfBlocks(), m_freelist);
//
//        m_fileOpen = true;
//        m_readCursor = 0;
//        m_numBytes = m_fcb->getFileSize();
    }
    // File doesn't exist
    else {
        if(m_mode == 'r') {
            cout << "ERROR: Variable 'mode' passed into File::File(name,mode) can not be r";
            cout << "when created a new file" << endl;
            throw new FileException();
        }

        m_fcb = new FCB(m_freelist);
        m_directory->Add(name, m_fcb->GetBlockNumber());
        m_blockgroup = m_freelist->CreateNew();
        m_fcb->setFileStartBlockNumber(m_blockgroup->GetStartBlockNumber());
        m_fcb->setFileEndBlockNumber(m_blockgroup->GetEndBlockNumber());
        m_fcb->setFileNumberOfBlocks(m_blockgroup->GetNumberOfBlocks());
        m_fcb->setFileSize(0);

        // Write new data to disk
        m_fcb->Write(m_disk);
        m_directory->Flush();

        m_fileOpen = true;
        m_readCursor = 0;
        m_numBytes = 0;
    }

}

/**
 * Open a closed file with re-initalizing the file. A file that already exists
 * needs to be opened before write and read can be called on the file. This
 * method can be called multiple times to open multiple different files.
 *
 */
void File::Open(char mode){
        //m_fcb = new FCB(m_directory->Find(name), m_disk);
    SwitchMode(mode);
    m_blockgroup = new BlockGroup(m_fcb->getFileStartBlockNumber(), m_fcb->getFileEndBlockNumber(),
            m_fcb->getFileNumberOfBlocks(), m_freelist);

    m_fileOpen = true;
    m_readCursor = 0;
    m_numBytes = m_fcb->getFileSize();

}

/**
 * Closes the open file so it can no longer be read or written to.
 *
 * @return Returns true if close was successful.
 */
bool File::Close(){
    bool result = false;
    if (m_fileOpen == false){
        printf("File is already closed \n");
        result = false;
    }
    else{
        delete m_blockgroup;
        printf("File close was successful");
        m_fileOpen = false;
        result = true;
    }
    return result;
}

/**
 *  The file deconstructor will free all allocated memory for this instance.
 *
 */
File::~File(){
    this->Close();
    delete m_fcb;
}

/**
 * The ByteCopy method copies the information in the source buffer to
 * the destination buffer.
 * When the user wants to write to the file, the source
 * buffer will be the user buffer containing the information that the user wants
 * written. The destination buffer will be the m_buffer of the file that the
 * user wants to write to. This will be opposite for reading a file.
 *
 * @param dest Pointer to a char* to the buffer that the information is copied to
 * @param source Pointer to char* to the buffer that the information is being copied from
 * @param length The length of both the buffer, since they need to be the same
 */
void File::ByteCopy(char* dest, char* source, int length){

    for (int i=0; i < length; i++){
        dest[i] = source[i];
    }
}

/**
 * The read method will read a specified length of bytes from an opened file that
 * is opened for reading (requires the mode value to equal 'r'). It will start
 * reading from the current cursor position
 * @param buffer Pointer to a char* to read the values from the file to. It
 * should already have allocated memory.
 * @param size The number of bytes to read from the opened file.
 * @return Returns the number of bytes read, or 0 when no bytes can be read.
 */
int File::Read(char* buffer, int size){
    Read(-1,buffer,size);
}

/**
 * The read method will read a specified length of bytes from an opened file that
 * is opened for reading (requires the mode value to equal 'r').
 *
 * @param lseek The offset for starting position for reading from file. -1 reads
 * from the location the last time read operation left it off at. 0 reads from
 * beginning of file.
 * @param buffer Pointer to a char* to read the values from the file to. It
 * should already have allocated memory.
 * @param size The number of bytes to read from the opened file.
 * @return Returns the number of bytes read, or 0 when no bytes can be read.
 */
int File::Read(int lseek, char* buffer, int size){
    printf("File::Read: ");
    if(!m_fileOpen){
        printf("File Not Open\n");
        return 0;
    }
    if(m_mode!='r'){
        printf("File not in Read mode");
        return 0;
    }
    if (lseek == -1 && m_numBytes - m_readCursor < size){
        size = m_numBytes - m_readCursor;//size = number of bytes left
        printf("You are trying to read more than available data. Only %d bytes"
                "will be read\n",size);
    }

    else if(lseek + size > m_numBytes){
        size = m_numBytes-lseek;
        m_readCursor = lseek;
        printf("You are trying to read more than available data. Only %d bytes"
                "will be read\n",size);
    }
    //e.g. byte index 508,1523. size 500, 1200 should be:
    //BlockOffsetIdx =508/508 = 1; 2 (2nd and 3rd blocks)
    //inBlockOffsetIdx = 508%508 +4= 4;507 (5th and 508th location)
    int BlockOffsetIdx = 1+(m_readCursor / Disk::DEFAULT_BLOCK_SIZE - 4);
    int inBlockOffsetIdx = 4+(m_readCursor % Disk::DEFAULT_BLOCK_SIZE - 4);
    if(BlockOffsetIdx > m_blockgroup->GetNumberOfBlocks() ||
            inBlockOffsetIdx > Disk::DEFAULT_BLOCK_SIZE -4){
        printf("Offset value error\n");
        return -1;}
    // (4+500)/512 +1 = 1;(507+1200)/512 +1 = 4;
    int blocksToRead = (inBlockOffsetIdx + size)/(Disk::DEFAULT_BLOCK_SIZE) +1;

    //if lseek was -1, CurrentBlock should be already set
    if(lseek != -1){
        m_blockgroup->Rewind();
        for(int i = 0; i<BlockOffsetIdx; i++)
            m_blockgroup->GetNextBlock();
    }

    int bytesLeftToRead = size;
    int writeIdx = 0;
    //Actual reading occurs here
    for(int i = 0; i < blocksToRead; i++){
        unsigned char* buf = m_blockgroup->GetCurrentBlock()->GetBuffer();
        //bytesLeftInBlock>bytesLeftToRead?bytesLeftToRead: bytesLeftInBlock

        int bytesFromCurrBlock =
        (Disk::DEFAULT_BLOCK_SIZE - inBlockOffsetIdx > bytesLeftToRead)?
            bytesLeftToRead:Disk::DEFAULT_BLOCK_SIZE - inBlockOffsetIdx;
        for(int j = 0; j<bytesFromCurrBlock;j++){
            buffer[writeIdx] = buf[inBlockOffsetIdx+j];
            writeIdx++;
            bytesLeftToRead--;
            //advance ccursor
            m_readCursor++;
        }
        m_blockgroup->GetNextBlock();
        inBlockOffsetIdx = sizeof(int);//set it to beginning for next block
    }
    return(size-bytesLeftToRead);//should equal size

}


/**
 * Write method writes a specified length of bytes to an opened file that is opened
 * for writing (requires the mode value to equal 'w').
 *
 * @param buffer Pointer to a char* where the bytes to be written are.
 * @param size The number of bytes to write to the opened file.
 * @return Returns the number of bytes written, or 0 when no bytes can be wrote.
 */
int File::Write(char* buffer, int size){

    if(!m_fileOpen){
        cout << "ERROR: File::Write File Not Open" << endl;
        throw new FileException();
    }
    if(m_mode!='w'){
        cout << "ERROR: File::Write Mode values does not equal w" << endl;
        throw new FileException();
    }

    int numBytesWritten = 0;

    int endOfFileInBuffer = (m_fcb->getFileSize() % (Disk::DEFAULT_BLOCK_SIZE - sizeof(int)));
    int numBlocksToWrite = (size + endOfFileInBuffer) /
            (Disk::DEFAULT_BLOCK_SIZE - sizeof(int));
    int remainder = (size + endOfFileInBuffer) %
            (Disk::DEFAULT_BLOCK_SIZE - sizeof(int));
    if (remainder > 0)
        numBlocksToWrite++;

    cout << "endOfFileInBuffer : " << endOfFileInBuffer << endl;
    cout << "numBlocksToWrite : " << numBlocksToWrite << endl;
    cout << "remainder : " << remainder << endl;

    // Offset for pointer in buffer
    endOfFileInBuffer = endOfFileInBuffer + sizeof(int);
    remainder = remainder + sizeof(int);


    Block currentBlock = Block(m_fcb->getFileEndBlockNumber(), m_disk);

    unsigned char* m_buffer = currentBlock.GetBuffer();

    int loop1;
    if(numBlocksToWrite > 1)
        loop1 = Disk::DEFAULT_BLOCK_SIZE;
    else
        loop1 = remainder;

    // Write the file till the end of the current block
    for(int i = endOfFileInBuffer; i < loop1; i++) {
        m_buffer[i] = buffer[i - endOfFileInBuffer];
        numBytesWritten++;
    }
    currentBlock.Write(m_disk);

    int bytesWrittenFirstBlock = numBytesWritten;

    // if there is still data to write, add more blocks
    if(numBlocksToWrite != 1) {
        
        for(int j = 0; j < numBlocksToWrite; j++) {

            if (m_freelist->GetNumberOfBlocks() == 0) {
                cout << "ERROR: File::Write: No more free blocks in the free list to finish writing the file" << endl;
                throw new FileException();
            }

            
            bool addBlockResult = m_blockgroup->AddBlock();
            Block * newBlock = new Block(m_blockgroup->GetEndBlockNumber(),m_disk);

            if(!addBlockResult) {
                cout << "ERROR: File::Write: Failed to add a block" << endl;
                throw new FileException();
            }

            currentBlock = *newBlock;
            
            unsigned char* m_buffer = currentBlock.GetBuffer();

            int loop2;
            if(numBlocksToWrite - j > 2)
                loop2 = Disk::DEFAULT_BLOCK_SIZE;
            else
                loop2 = remainder;

            // Write the file till the end of the current block
            for(int i = sizeof(int); i < loop2; i++) {
                m_buffer[i] = buffer[bytesWrittenFirstBlock + ((Disk::DEFAULT_BLOCK_SIZE - sizeof(int)) * j) + i - sizeof(int)];
                numBytesWritten++;
            }

            currentBlock.Write(m_disk);
            delete newBlock;
        }
        
    }

return numBytesWritten; 

}

/**
 * Lists information about each file entry in the Directory in RAM.
 * The information includes Filename, FCB, start and end block,
 * number of block associated with the file, and number of bytes in the file
 */
void File::List(){
    printf("Directory:: List: \n");

    vector<DirectoryEntry> tempDir = m_directory->List();
    for(unsigned int i = 0; i < tempDir.size(); i++){
        DirectoryEntry dir = tempDir[i];
        FCB tempFCB = FCB(dir.GetFileControlBlock(), m_disk);
        printf("Dir Entry: %s, %d\n",dir.GetFileName(),dir.GetFileControlBlock());
        printf("    File Start Block Number: %d\n", tempFCB.getFileStartBlockNumber());
        printf("    File End Block Number: %d\n", tempFCB.getFileEndBlockNumber());
        printf("    Number of Blocks: %d\n", tempFCB.getFileNumberOfBlocks());
        printf("    Number of Bytes: %d\n", tempFCB.getFileSize());
        
    }
}

void File::SwitchMode(char m){
    if(m == 'r' || m =='w')
        m_mode = m;
    else printf("invalid mode %c provided",m);
}




/**
 * Deletes the specified file, associated fcb, and directory entry. 
 * Assume the file is close before the delete method is called.
 * Then flushes the changes in ram to disk.
 * Assumes that the file is not opened for either reading or writing.
 *
 * @param name Filename for file to be deleted
 * @return Returns true if delete was successful.
 */
bool File::Delete(char* name){
    printf("File::Delete :");
    bool result = false;
    int fcb;
    fcb = m_directory->Find(name);
    FCB b = FCB(fcb, m_disk);

    int startBlock, endBlock, numberOfBlocks;
    startBlock = b.FILE_START_BLOCK_NUMBER;
    endBlock = b.FILE_END_BLOCK_NUMBER;
    numberOfBlocks = b.FILE_NUMBER_OF_BLOCKS;

    BlockGroup del = BlockGroup(startBlock,endBlock,numberOfBlocks,m_freelist);
    m_freelist->Return(&del);
    m_freelist->AddBlock(&b);
    if(m_directory->Delete(name)){
        printf("%s success! \n", name);
        result = true;
    }
    return result;
}

/**
 * Exists method checks the directory to see if the file already exists.
 * 
 * @param name Filename to check to see if it already exists in the directory
 * @return Returns true if the file exists.
 */
bool File::Exists(char* name) {

    if(m_directory->Find(name) == -1){
        printf("%s does not exist \n", name);
       return false;
    }
    else{
        printf("%s already exists \n");
        return true;
    }
}

/**
 * Test method to verify all File constructors and methods work properly.
 * 
 */
void File::Test(){

    FreeList *testFreeList;// = new FreeList("Disk1.disk",true);
    Directory* testDirectory = NULL;
    
    
    
    testFreeList = new FreeList("Disk1.disk",true);
    testDirectory = new Directory(testFreeList,true);
    File::Initialize(testFreeList, testDirectory);

    File f1("File1",'w');
    f1.Write("Giggle if you google", 21);
    char buff[21];
    f1.SwitchMode('r');
    f1.Read(buff,21);
    printf("read:%s\n");

    } // end Test()

    FreeList *File::m_freelist = NULL;
    Directory *File::m_directory = NULL;
    Disk *File::m_disk = NULL;
